
#include "at45db.h"
#include "app_util_platform.h"
#include "nrf_gpio.h"
#include "string.h"

void spi_event_handler(nrf_drv_spi_evt_t const * p_event)
{
    spi_xfer_done = true;
}

#define wait_until_xfer_done()  \
    do {                        \
        while(!spi_xfer_done);  \
        spi_xfer_done = false;  \
    } while(0)

#define start_spi_xfer(PIN)     \
    do {                        \
        spi_xfer_done = false;  \
        nrf_gpio_pin_clear(PIN);\
    } while(0)

#define stop_spi_xfer(PIN)      \
    do {                        \
        nrf_gpio_pin_set(PIN);  \
    } while(0)

#define START_XFER() start_spi_xfer(SPI_CS_PIN)
#define STOP_XFER() stop_spi_xfer(SPI_CS_PIN)

uint32_t at45db_read_flash_id(const nrf_drv_spi_t *spi) {
    uint32_t flash_id = 0;
    uint8_t cmd = { READ_FLASH_ID };
    spi_xfer_done = false;

    START_XFER();

    nrf_drv_spi_transfer(spi, &cmd, 1, (uint8_t *)&flash_id, 0);
    while(!spi_xfer_done)    ;

    nrf_drv_spi_transfer(spi, &cmd, 0, (uint8_t *)&flash_id, 4);
    while(!spi_xfer_done)    ;

    STOP_XFER();
    return flash_id;
}

void at45db_erase_chip(const nrf_drv_spi_t *spi) {

    uint8_t cmd[4] = {0xc7, 0x94, 0x80, 0x9a};
    uint8_t dummy, status;

    START_XFER();
    nrf_drv_spi_transfer(spi, cmd, 4, &dummy, 0);
    wait_until_xfer_done();
    STOP_XFER();

}

void at45db_init() {
    at45db_spi_init(&spi);
}

uint32_t at45db_spi_init(const nrf_drv_spi_t *spi) {
    nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG(SPI_INSTANCE);
    return nrf_drv_spi_init(spi, &spi_config, spi_event_handler);
}

void at45db_read_buffer1(const nrf_drv_spi_t *spi) {
    uint8_t cmd[5] = {BUFFER_1_READ, 0x00, 0x00, 0x00, 0x00};
    uint8_t dat[3] = {0xff, 0xff};

    START_XFER();

    // send read command
    nrf_drv_spi_transfer(spi, cmd, sizeof(cmd), dat, 0);
    wait_until_xfer_done();
    // read one byte
    nrf_drv_spi_transfer(spi, cmd, 0, dat, sizeof(dat));
    wait_until_xfer_done();

    STOP_XFER();

    printf("%x %x %x\n", dat[0], dat[1], dat[2]);
}

void at45db_write_buffer1(const nrf_drv_spi_t *spi) {
    uint8_t cmd[] = {BUFFER_1_WRITE, 0x00, 0x0, 0x00};
    uint8_t dat[] = {0xaa, 0xbb};

    START_XFER();

    // send cmd
    nrf_drv_spi_transfer(spi, cmd, sizeof(cmd), NULL, 0);
    wait_until_xfer_done();
    // send data
    nrf_drv_spi_transfer(spi, dat, sizeof(dat), NULL, 0);
    wait_until_xfer_done();

    STOP_XFER();
}


void at45db_memory_page_write_through_buffer1(const nrf_drv_spi_t *spi,
                                                int32_t page_id,
                                                uint8_t *dat, uint32_t len) {
    if(page_id >= PAGE_NUM)
        return;
    if(len > PAGE_SIZE || len < 1)
        return;

    uint8_t pg_id_high = (uint8_t)(page_id >> 8);
    uint8_t pg_id_low = (uint8_t)(page_id & 0xff);
    uint8_t dummy;

    uint8_t cmd[4] = {MEMORY_PAGE_WRITE_THROUGH_BUFFER_1, pg_id_high, pg_id_low, 0x00};

    START_XFER();

    APP_ERROR_CHECK(nrf_drv_spi_transfer(spi, cmd, sizeof(cmd), &dummy, 0));
    wait_until_xfer_done();

    if(len < PAGE_SIZE ) {
        APP_ERROR_CHECK(nrf_drv_spi_transfer(spi, dat, len, &dummy, 0));
        wait_until_xfer_done();
    } else if(len == PAGE_SIZE) {

        uint8_t half_len = PAGE_SIZE / 2;

        APP_ERROR_CHECK(nrf_drv_spi_transfer(spi, dat, half_len, &dummy, 0));
        wait_until_xfer_done();

        APP_ERROR_CHECK(nrf_drv_spi_transfer(spi, dat + half_len, half_len, &dummy, 0));
        wait_until_xfer_done();
    }
    STOP_XFER();
}

void at45db_memory_page_read(const nrf_drv_spi_t *spi,
                                int32_t page_id,
                                uint8_t *dat, uint32_t len) {

    if(page_id >= PAGE_NUM)
        return;
    if(len > PAGE_SIZE || len < 1)
        return;

    uint8_t pg_id_high = (uint8_t)(page_id >> 8);
    uint8_t pg_id_low = (uint8_t)(page_id & 0xff);

    uint8_t cmd[8] = {MEMORY_PAGE_READ, pg_id_high, pg_id_low, 0x00, \
                    0xff, 0xff, 0xff, 0xff};

    START_XFER();

    //send read cmd
    nrf_drv_spi_transfer(spi, cmd, sizeof(cmd), dat, 0);
    wait_until_xfer_done();

    if(len < PAGE_SIZE) {
        //read len bytes
        nrf_drv_spi_transfer(spi, cmd, 0, dat, 128);
        wait_until_xfer_done();
    } else if(len == PAGE_SIZE) {
        uint8_t half_len = PAGE_SIZE / 2;

        nrf_drv_spi_transfer(spi, cmd, 0, dat, half_len);
        wait_until_xfer_done();

        nrf_drv_spi_transfer(spi, cmd, 0, dat+half_len, half_len);
        wait_until_xfer_done();
    }

    STOP_XFER();
}

uint8_t at45db_read_status(const nrf_drv_spi_t *spi) {

        uint8_t cmd[] = {READ_STATUS};
        uint8_t ret[2];

        START_XFER();
        nrf_drv_spi_transfer(spi, cmd, sizeof(cmd), ret, 2);
        wait_until_xfer_done();
        STOP_XFER();

        return ret[1];
}

uint32_t flash_init_ctx(struct flash_ctx *ctx, uint8_t spi_instance) {

    uint32_t ret = 0;
    ctx->spi = (nrf_drv_spi_t *)&spi;
    ret = at45db_spi_init(ctx->spi);
    return ret;
}

uint32_t flash_get_id(struct flash_ctx *ctx) {
    return at45db_read_flash_id(ctx->spi);
}

// send two bytes to write buffer 1
// reve tow bytes from read buffer 1
uint32_t flash_test(struct flash_ctx *ctx) {
    at45db_write_buffer1(ctx->spi);
    at45db_read_buffer1(ctx->spi);
    return 0;
}

uint32_t flash_read_page(struct flash_ctx *ctx,
                            int32_t page_id,
                            uint8_t *data, uint32_t len) {

    memset(data, 0, len);
    at45db_memory_page_read(ctx->spi, 0, data, len);
    return 0;
}

uint32_t flash_write_page(struct flash_ctx *ctx,
                            int32_t page_id,
                            uint8_t *data, uint32_t len) {

    int i = 0;
    memset(data, 0, len);
    for(i=0; i < len; i++)
        data[i] = PAGE_SIZE - i - 1;

    at45db_memory_page_write_through_buffer1(ctx->spi, 0, data, len);
    return 0;
}

void flash_erase_chip(struct flash_ctx *ctx) {
    at45db_erase_chip(ctx->spi);
    while(flash_check_busy(ctx));
}

uint8_t flash_check_busy(struct flash_ctx *ctx) {
    uint8_t status = at45db_read_status(ctx->spi);
    return !(status & 0x80);    // low->busy, high -> not busy
}
